package com.nus.frogworks;

import java.io.File;
import java.io.FileWriter;
import java.util.ArrayList;

import android.location.Location;
import android.os.Environment;
import android.os.SystemClock;

public class PiWrapper implements DataInputWrapper {

	//Data Storage
	private ArrayList<Double> _speedList = new ArrayList<Double>();
	private ArrayList<Double> _voltageList = new ArrayList<Double>();
	private ArrayList<Double> _currentList = new ArrayList<Double>();
	private ArrayList<Double> _motorList = new ArrayList<Double>();
	private ArrayList<Double> _ambientList = new ArrayList<Double>();
	private ArrayList<Double> _batteryList = new ArrayList<Double>();
	private ArrayList<Double> _controllerList = new ArrayList<Double>();
	private ArrayList<Integer> _timeList = new ArrayList<Integer>();
	
	private final int MAX_BATTERY_CAPACITY = 2000;
	private double initialCapacity;
	private double batteryCapacity;
	private double chargeConsumed;
	
	private long startTime;
	private long lastLogTime;
	private long lastBackupTime;
	
	private Location location = null;
	private boolean logLocation = false;
	
	StringBuilder logUpdate;
	
	//Constant Divisors
	private final double SPEED_DIVISOR = 100.0;
	private final double CURRENT_DIVISOR = 100.0;
	private final double VOLTAGE_DIVISOR = 100.0;
	private final double TEMPERATURE_DIVISOR = 10.0;
	
	//Data Interval Constants
	private final int SPEED_INTERVAL = 0;
	private final int CURRENT_INTERVAL = 4;
	private final int VOLTAGE_INTERVAL = 8;
	private final int MOTOR_INTERVAL = 12;
	private final int AMBIENT_INTERVAL = 16;
	private final int BATTERY_INTERVAL = 20;
	private final int CONTROLLER_INTERVAL = 24;
	
	//Other Useful Constants
	private final int BYTES_IN_INT = 4;
	private final int BITMASK = 0x000000FF;
	private final int BITS_IN_BYTE = 8;
	
	private final String NO_READING = "--";
	
	private final char LOG_COL_DELIMITER = ',';
	private final char LOG_ROW_DELIMITER = '\n';
	
	//Units
	private final String TIME_TITLE = "Time / ms";
	private final String SPEED_TITLE = "Speed / kmph";
	private final String CURRENT_TITLE = "Current / A";
	private final String VOLTAGE_TITLE = "Voltage / V";
	private final String MOTOR_TITLE = "Motor Temp / C";
	private final String AMBIENT_TITLE = "Ambient Temp / C";
	private final String BATTERY_TITLE = "Battery Temp / C";
	private final String CONTROLLER_TITLE = "Controller Temp / C";
	
	private final String LATITUDE_TITLE = "Latitude / Deg";
	private final String LONGITUDE_TITLE = "Longitude / Deg";
	private final String ALTITUDE_TITLE = "Altitude / m";
	
	private int logFrequency = 100; //in milliseconds
	private int backupFrequency = 18000;
	
	private AccessoryEngine mEngine = null;
	
	File logFile;
	boolean toLog;
	
	public PiWrapper(AccessoryEngine input, String filename){
		mEngine = input;
		logFile = getLogStorageDir(filename);
		toLog = true;
		writeLogTitle();
	}
	
	private void writeLogTitle() {
		
		logUpdate.append(TIME_TITLE);
		logUpdate.append(LOG_COL_DELIMITER);
		
		logUpdate.append(SPEED_TITLE);
		logUpdate.append(LOG_COL_DELIMITER);

		logUpdate.append(VOLTAGE_TITLE);
		logUpdate.append(LOG_COL_DELIMITER);
		
		logUpdate.append(CURRENT_TITLE);
		logUpdate.append(LOG_COL_DELIMITER);
		
		logUpdate.append(MOTOR_TITLE);
		logUpdate.append(LOG_COL_DELIMITER);
		
		logUpdate.append(AMBIENT_TITLE);
		logUpdate.append(LOG_COL_DELIMITER);
		
		logUpdate.append(BATTERY_TITLE);
		logUpdate.append(LOG_COL_DELIMITER);
		
		logUpdate.append(CONTROLLER_TITLE);
		logUpdate.append(LOG_COL_DELIMITER);
		
		if(logLocation){
			
			logUpdate.append(LATITUDE_TITLE);
			logUpdate.append(LOG_COL_DELIMITER);
			
			logUpdate.append(LONGITUDE_TITLE);
			logUpdate.append(LOG_COL_DELIMITER);
			
			logUpdate.append(ALTITUDE_TITLE);
			logUpdate.append(LOG_COL_DELIMITER);
		}
		
		logUpdate.append(LOG_ROW_DELIMITER);
		
	}

	public PiWrapper(AccessoryEngine input){
		mEngine = input;
		toLog = false;
	}
	
	private File getLogStorageDir(String logname){
		File file = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS),logname);
		return file;
	}
	
	public void writeEmptySignal(){
    	byte[] tokenSignal = new byte[1];
    	tokenSignal[0] = 0;
    	writeToPi(tokenSignal);
	}
	
	public void write(byte[] toWrite){
		writeToPi(toWrite);
	}
	
	protected void writeToPi(byte[] toWrite){
		mEngine.write(toWrite);
	}
	
	public void updateData(byte[] newData){
		updateSpeed(newData);
		updateVoltage(newData);
		updateCurrent(newData);
		updateMotor(newData);
		updateAmbient(newData);
		updateBattery(newData);
		updateController(newData);
		updateCapacity();
		updateTime();
	}
	
	public String getSpeedString(){
		return getStringLatest(_speedList);
	}
	
	public String getVoltageString(){
		return getStringLatest(_voltageList);
	}
	
	public String getCurrentString(){
		return getStringLatest(_currentList);
	}
	
	public String getMotorString(){
		return getStringLatest(_motorList);
	}
	
	public String getAmbientString(){
		return getStringLatest(_ambientList);
	}
	
	public String getBatteryString(){
		return getStringLatest(_batteryList);
	}
	
	public String getControllerString(){
		return getStringLatest(_controllerList);
	}
	
	public String getCapacityString(){
		return String.valueOf((int)batteryCapacity*100/MAX_BATTERY_CAPACITY);
	}
	
	public int getMaxCapacity(){
		return MAX_BATTERY_CAPACITY;
	}
	
	public int getBattCapacity(){
		return (int) batteryCapacity;
	}
	
	public void updateLogs() throws Exception{
		
		long currTime = SystemClock.elapsedRealtime();
		
		if(toLog && currTime - lastLogTime > logFrequency){
			
			logUpdate.append(currTime);
			logUpdate.append(LOG_COL_DELIMITER);
			
			logUpdate.append(getSpeedString());
			logUpdate.append(LOG_COL_DELIMITER);
			
			logUpdate.append(getVoltageString());
			logUpdate.append(LOG_COL_DELIMITER);
			
			logUpdate.append(getCurrentString());
			logUpdate.append(LOG_COL_DELIMITER);
			
			logUpdate.append(getMotorString());
			logUpdate.append(LOG_COL_DELIMITER);
			
			logUpdate.append(getAmbientString());
			logUpdate.append(LOG_COL_DELIMITER);
			
			logUpdate.append(getBatteryString());
			logUpdate.append(LOG_COL_DELIMITER);
			
			logUpdate.append(getControllerString());
			logUpdate.append(LOG_COL_DELIMITER);
			
			if(logLocation){
				logUpdate.append(location.getLatitude());
				logUpdate.append(LOG_COL_DELIMITER);
				
				logUpdate.append(location.getLongitude());
				logUpdate.append(LOG_COL_DELIMITER);
				
				logUpdate.append(location.getAltitude());
				logUpdate.append(LOG_COL_DELIMITER);
			}
			
			logUpdate.append(LOG_ROW_DELIMITER);
			
			lastLogTime = SystemClock.elapsedRealtime(); 
		}
		
		if(toLog && currTime - lastBackupTime > backupFrequency){
			
			FileWriter logFileWriter = new FileWriter(logFile,true);
			
			logFileWriter.append(logUpdate.toString());

			logFileWriter.flush();
			logFileWriter.close();
			
			logUpdate = new StringBuilder();
			
			lastBackupTime = SystemClock.elapsedRealtime();
		}
		
	}
	
	public void updateInitialCapacity(byte[] data){
		int capacityInteger = 0;
		for(int i = 0; i < BYTES_IN_INT; i++){
			int shift = (BYTES_IN_INT - 1 - i) * BITS_IN_BYTE;
	    	capacityInteger += (data[i] & BITMASK) << shift;
	    }
		initialCapacity = capacityInteger;
		batteryCapacity = initialCapacity;
		L.d("battery capacity is " + batteryCapacity);
		chargeConsumed = 0;
		startTime = SystemClock.elapsedRealtime();
		lastLogTime = startTime;
		lastBackupTime = startTime;
	}
	
	private void updateSpeed(byte[] data){
		int speedInteger = 0;
	    for (int i = 0; i < BYTES_IN_INT; i++) {
	    	int shift = (BYTES_IN_INT - 1 - i) * BITS_IN_BYTE;
	    	speedInteger += (data[i + SPEED_INTERVAL] & BITMASK) << shift;
	    }
	    _speedList.add((double) (speedInteger / SPEED_DIVISOR) );
	}
	
	private void updateVoltage(byte[] data){
		int voltageInteger = 0;
	    for (int i = 0; i < BYTES_IN_INT; i++) {
	    	int shift = (BYTES_IN_INT - 1 - i) * BITS_IN_BYTE;
	    	voltageInteger += (data[i + VOLTAGE_INTERVAL] & BITMASK) << shift;
	    }
	    _voltageList.add((double) (voltageInteger / VOLTAGE_DIVISOR) );
	}
	
	private void updateCurrent(byte[] data){
		int currentInteger = 0;
	    for (int i = 0; i < BYTES_IN_INT; i++) {
	    	int shift = (BYTES_IN_INT - 1 - i) * BITS_IN_BYTE;
	    	currentInteger += (data[i + CURRENT_INTERVAL] & BITMASK) << shift;
	    }
	    _currentList.add((double) (currentInteger / CURRENT_DIVISOR) );
	}
	
	private void updateMotor(byte[] data){
		int motorInteger = 0;
	    for (int i = 0; i < BYTES_IN_INT; i++) {
	    	int shift = (BYTES_IN_INT - 1 - i) * BITS_IN_BYTE;
	    	motorInteger += (data[i + MOTOR_INTERVAL] & BITMASK) << shift;
	    }
	    _motorList.add((double) (motorInteger / TEMPERATURE_DIVISOR) );
	}
	
	private void updateAmbient(byte[] data){
		int ambientInteger = 0;
	    for (int i = 0; i < BYTES_IN_INT; i++) {
	    	int shift = (BYTES_IN_INT - 1 - i) * BITS_IN_BYTE;
	    	ambientInteger += (data[i + AMBIENT_INTERVAL] & BITMASK) << shift;
	    }
	    _ambientList.add((double) (ambientInteger / TEMPERATURE_DIVISOR) );
	}
	
	private void updateBattery(byte[] data){
		int batteryInteger = 0;
	    for (int i = 0; i < BYTES_IN_INT; i++) {
	    	int shift = (BYTES_IN_INT - 1 - i) * BITS_IN_BYTE;
	    	batteryInteger += (data[i + BATTERY_INTERVAL] & BITMASK) << shift;
	    }
	    _batteryList.add((double) (batteryInteger / TEMPERATURE_DIVISOR) );
	}
	
	private void updateController(byte[] data){
		int controllerInteger = 0;
	    for (int i = 0; i < BYTES_IN_INT; i++) {
	    	int shift = (BYTES_IN_INT - 1 - i) * BITS_IN_BYTE;
	    	controllerInteger += (data[i + CONTROLLER_INTERVAL] & BITMASK) << shift;
	    }
	    _controllerList.add((double) (controllerInteger / TEMPERATURE_DIVISOR) );
	}
	
	private String getStringLatest(ArrayList<Double> dataList){
		if (dataList.isEmpty()){
			return NO_READING;
		} else {
			return String.valueOf(dataList.get(dataList.size()-1));
		}
	}
	
	private void updateCapacity(){
		//L.d("battery capacity is " + batteryCapacity);
		if (_timeList.size()>1){
			int latestTime = _timeList.size()-1;
			int latestCurr = _currentList.size()-1;
			chargeConsumed += (_timeList.get(latestTime) - _timeList.get(latestTime-1)) 
					* _currentList.get(latestCurr);
			batteryCapacity = initialCapacity - chargeConsumed;
		}
	}
	
	private void updateTime(){
		_timeList.add((int)(SystemClock.elapsedRealtime()-startTime));
	}

	@Override
	public void setLogFrequency(int freq) {
		logFrequency = freq;
	}

	@Override
	public void setBackupFrequency(int freq) {
		backupFrequency = freq * 60 * 60;
	}

	@Override
	public void setLocation(Location loc) {
		location = loc;
	}
	
	@Override
	public void enableLocationLogging(){
		if (toLog){
			logLocation = true;
		}
	}
	
}
